/*
 * HTMLParser.cpp
 *
 *  Created on: 2010-6-10
 *      Author: guobc
 */

#include "HTMLParser.h"
#include "Tools.h"
#include "ParserException.h"

namespace HA {
HTMLParser::HTMLParser(Lexer* lexer) :
	HA::AbstractParser(lexer), mRoot(NULL) {
}

void HTMLParser::parse() throw (ParserException) {
	try {
		//		if (!mLexer->isOpen()) {
		////			return;
		//			throw ParserException(__FUNCTION__, __LINE__, "HTMLParser");
		//		}

		Token* token;
		int type;
		string text;

		bool beginTag = false;
		bool endTag = false;

		NodeFactory factory = getNodeFactory();
		//		ASTDom* document = getDocument();
		Element* element = NULL;
		Element* parent = NULL;

		int start = -1;
		int end = -1;

		while (true) {
			token = mLexer->nextToken();
			if (token == NULL || endTag) {
				break;
			}
			type = token->getType();
			text = token->getText();

			if (type == tOAglBkt) {
				beginTag = true;
			} else if (type == tCAglBkt) {
				beginTag = false;
			} else if (beginTag && type == tExcmtMak) {
				parseCommentOrDoctype();
			} else if (beginTag && type == tJSP) {
				element = &parseJSP(beginTag, *token);
				parent->appendChild(element);
				element->setParent(parent);
			} else if (beginTag && (type == tSymbol || type == tSlash)) {
				start = end = mLexer->getLineNumber();
				Tag tag = parseTag(beginTag, *token);
				if (!tag.isEndTag()) {
					string name = tag.getTagName();
					element = factory.createElementNode(name, start, end);
					element->setElementName(name);
					element->setStartLine(start);
					element->setStartTag(tag);
					element->setParent(parent);
					if ("html" == Tools::toLowerCase(name)) {
						mRoot = element;
						mRoot->setParent(NULL);
					}
					if (parent != NULL) {
						parent->appendChild(element);
					}
					parent = element;
				} else {
					element->setEndLine(end);
					element->setEndTag(tag);
					parent = dynamic_cast<Element*> (element->getParent());
					element = parent;
				}

			} else if (!beginTag) {//finish parse tag and may go to parse string
				Text* textNode = &parseString(beginTag, *token);
				textNode->setParent(parent);
				parent->appendChild(textNode);
			}
		}
	} catch (ParserException& e) {
		//		cout << e.what();
		throw e;
		//		throw ParserException(__FUNCTION__, __LINE__, "HTMLParser");
	}
}

void HTMLParser::parseCommentOrDoctype() throw (ParserException) {
	Token* token;
	int type;
	string text;
	string contents = "";

	int n = 0;

	ASTNode* node = NULL;
	while (true) {
		token = mLexer->nextToken();
		if (token == NULL)
			break;
		type = token->getType();
		text = token->getText();
		n++;
		if (type == tDoubleHyphen) {
			if (n == 1) {
				node = getNodeFactory().createCommentNode("",
						token->getOffset(), token->getOffset());
			}
		} else if (type == tSymbol && n == 1) {
			string& tmpText = text;
			if ("DOCTYPE" == Tools::toUpperCase(tmpText)) {
				node = getNodeFactory().createElementNode("!DOCTYPE",
						token->getOffset(), -1);
			} else {
				node = getNodeFactory().createCommentNode("",
						token->getOffset(), token->getOffset());
			}
		} else if (type == tCAglBkt) {
			node->setEndLine(token->getLine());
			break;
		} else {
			contents += text;
		}
	}
	node->setText(contents);
}

Tag HTMLParser::parseTag(bool& beginTag, Token& token) throw (ParserException) {
	Tag tag;
	Token* next;
	int type = token.getType();
	string text;
	if (type == tSlash) {//end tag
		beginTag = false;
		next = mLexer->nextToken();
		if (next == NULL) {
			throw ParserException(__FUNCTION__, __LINE__, "HTMLParser");
		}
		text = next->getText();
		tag.setTagName("/" + text);
	} else { // begin tag
		text = token.getText();
		tag.setTagName(text);
		//		if (skipWhiteSpace() == false) {
		//			throw ParserException(__FUNCTION__, __LINE__, "HTMLParser");
		//		}
		string key;
		string value;
		bool assignment = false;
		bool attriEnd = false;//finish parse a attribute
		while (true) {
			next = mLexer->nextToken();
			if (next == NULL)
				break;
			type = next->getType();
			text = next->getText();
			if (type == tCAglBkt) {
				beginTag = false;
				break;
			} else if (!assignment && type == tSymbol) {
				key = text;
			} else if (type == tAssignment) {
				assignment = true;
			} else if (assignment && (type == tQuotationString || type
					== tSymbol)) {
				value = text;
				assignment = false;
				attriEnd = true;
			} else if (type == tSlash) {
				/**
				 * This is a tag with no close tag.
				 * We take the slash as a attribute of this tag.
				 */
				key = text;
				value = text;
				attriEnd = true;
			} else if (type == tWhiteSpace) {//skip white space.
				continue;
			}

			if (attriEnd) {
				tag.setAttribute(key, value);
				attriEnd = false;
			}

		}
	}

	return tag;
}

Text& HTMLParser::parseString(bool& beginTag, Token& token)
		throw (ParserException) {
	int start = mLexer->getLineNumber();
	int end = -1;
	Text* textNode = getNodeFactory().createStringNode("", start, end);
	string content = token.getText();
	Token* next;
	int type = token.getType();
	while (true) {
		next = mLexer->nextToken();
		if (next == NULL)
			break;
		type = next->getType();
		if (type == tOAglBkt) {
			beginTag = true;
			break;
		}
		content += next->getText();
		end = mLexer->getLineNumber();
	}

	textNode->setEndLine(end);
	textNode->setText(content);
	return *textNode;
}

Element& HTMLParser::parseJSP(bool& beginTag, Token& token)
		throw (ParserException) {
	int start = mLexer->getLineNumber();
	int end = -1;

	Token* next;
	int type = token.getType();
	string name = token.getText();
	string content = token.getText();

	Element* jspElement = getNodeFactory().createElementNode(name, start, end);
	jspElement->setElementName(name);

	while (true) {
		if (type == tJSP) {//end with '%>' in the JSP element
			end = mLexer->getLineNumber();
			break;
		}
		next = mLexer->nextToken();
		if (next == NULL)
			break;
		type = next->getType();
		content += next->getText();

	}

	jspElement->setText(content);
	jspElement->setEndLine(end);
	return *jspElement;
}

bool HTMLParser::skipWhiteSpace() {
	Token* token = mLexer->nextToken();
	if (token->getType() == tWhiteSpace) {
		return true;
	}
	return false;
}

}
